Fundamentals of NumPy

This notebook aims to quickly walk you through the most fundamental bits of NumPy, including:

  1. how to create/initiate 1D arrays and 2D matrices,
  2. how to get/set the shape of numpy arrays,
  3. and how to calculate the dot product of two NumPy arrays, and take the norms.

This notebook is created by cherry-picking from the official documents of NumPy. Please refer to that page for more information.


In [6]:
import numpy as np

1. ndarray in NumPy

NumPy’s main object is the homogeneous multidimensional array (ndarray). It is a table of elements (usually numbers), all of the same type, indexed by a tuple of positive integers.

A list of elements can be expressed as an ndarray of rank 1, i.e. a 1D array; a matrix can be expressed as an ndarray of rank 2.

Here lists some important attributes of ndarray:

  • ndarray.ndim: the rank of the ndarray. For instance, a matrix has a rank of 2.
  • ndarray.shape: the dimensions of the ndarray as a tuple of integers. For an array having 10 elements, the shape is (10,) (note the trailing comma, not the same as (10, 1)); for a matrix having 20 rows and 30 columns, the shape is (20, 30).
  • ndarray.size: the total number of elements in the ndarray, which is equal to the product of the dimensions.
  • ndarray.dtype: an object describing the type of the elements in the array.

In [7]:
arr = np.array(range(1, 10))
print('arr\t\t', arr)
print('arr.ndim\t', arr.ndim)
print('arr.shape\t', arr.shape)
print('arr.size\t', arr.size)
print('arr.dtype\t', arr.dtype)


arr		 [1 2 3 4 5 6 7 8 9]
arr.ndim	 1
arr.shape	 (9,)
arr.size	 9
arr.dtype	 int64

2. Array Creation

There are several ways to create ndarrays.

2.1 Using the array function

You can create a 1D ndarray from an existing list/array easily using the array function.


In [8]:
np.array([1, 2, 3, 4])


Out[8]:
array([1, 2, 3, 4])

Note that there is only one argument. So never do this:


In [9]:
# Do not do this
#np.array(1, 2, 3, 4)

To create a matrix, call array on a sequence of sequence.


In [10]:
x = np.array([[1, 2], [3, 4], [5, 6]])
x


Out[10]:
array([[1, 2],
       [3, 4],
       [5, 6]])

In [11]:
x.shape


Out[11]:
(3, 2)

2.2 Using zeros, ones, empty

When the contents of the array to be created are unknown, but its dimensions are known, use one of zeros, ones, empty.


In [12]:
np.zeros((2, 3))  # the elements are explicitly initialized to zeros


Out[12]:
array([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])

In [13]:
np.ones((2, 3))  # the elements are explicitly initialized to ones


Out[13]:
array([[ 1.,  1.,  1.],
       [ 1.,  1.,  1.]])

In [14]:
np.empty((2, 3))  # the elements are not explicitly initialized; expect random values


Out[14]:
array([[ 1.,  1.,  1.],
       [ 1.,  1.,  1.]])

The default dtype is numpy.float64 for these functions.

2.3 Using arange, linspace

Akin to range in Python, arange in NumPy returns a sequence of numbers in a ndarray. Use the dtype parameter to change the type, or use astype() function to cast into another type.


In [15]:
np.arange(1, 3, 0.2)


Out[15]:
array([ 1. ,  1.2,  1.4,  1.6,  1.8,  2. ,  2.2,  2.4,  2.6,  2.8])

Due to the finite precision of floating point numbers, however, it's better to use linspace when we are trying to create a sequence of floating point numbers, specifying how many elements we want, instead of the step.


In [16]:
np.linspace(1, 3, 7)


Out[16]:
array([ 1.        ,  1.33333333,  1.66666667,  2.        ,  2.33333333,
        2.66666667,  3.        ])

3. Playing with the Shapes of ndarrays


In [17]:
arr = np.arange(1, 10)
arr


Out[17]:
array([1, 2, 3, 4, 5, 6, 7, 8, 9])

3.1 How to Get the Shape of an ndarray


In [18]:
arr.shape


Out[18]:
(9,)

3.2 How to Reshape the ndarray:


In [19]:
arr.reshape((3, 3))


Out[19]:
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

The reshape function returns a new ndarray with the shape changed without modifying the original one.


In [20]:
arr


Out[20]:
array([1, 2, 3, 4, 5, 6, 7, 8, 9])

In [21]:
arr.reshape((9,1))


Out[21]:
array([[1],
       [2],
       [3],
       [4],
       [5],
       [6],
       [7],
       [8],
       [9]])

To directly modify the shape of an ndarray:


In [22]:
arr.shape = (3, 3)
arr


Out[22]:
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

Note that 1d arrays and 2d arrays are different.


In [23]:
xx = arr.reshape((1,9))
xx


Out[23]:
array([[1, 2, 3, 4, 5, 6, 7, 8, 9]])

In [24]:
xx.shape


Out[24]:
(1, 9)

In [25]:
xx[0]


Out[25]:
array([1, 2, 3, 4, 5, 6, 7, 8, 9])

In [26]:
arr


Out[26]:
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

In [27]:
arr.shape


Out[27]:
(3, 3)

4. Basic Operations

4.1 Arithmetic Operators

Arithmetic operators on ndarrays apply elementwise (so the operation is vectorized). A new ndarray will be created to hold the result.


In [28]:
arr = np.array([1, 2])

In [29]:
arr + 1


Out[29]:
array([2, 3])

In [30]:
arr * 2


Out[30]:
array([2, 4])

In [31]:
arr ** 2


Out[31]:
array([1, 4])

4.2 Dot Products

Given two ndarrays with proper shapes:


In [32]:
a = np.array([1, 2])
a


Out[32]:
array([1, 2])

In [33]:
b = np.array([[1], [2]])
b


Out[33]:
array([[1],
       [2]])

To calculate the dot product, the sentence in the following cell is intuitive but WRONG:


In [34]:
a * b  # calculating elementwise product!


Out[34]:
array([[1, 2],
       [2, 4]])

To correctly calculate the dot products of two ndarrays, use numpy.dot or the dot function on the ndarray object.


In [35]:
a.dot(b)


Out[35]:
array([5])

In [36]:
np.dot(a, b)


Out[36]:
array([5])

Both ways create a new ndarray to hold the results without modifying the original ones.


In [37]:
a = np.arange(3)-1
a


Out[37]:
array([-1,  0,  1])

In [38]:
np.linalg.norm(a)


Out[38]:
1.4142135623730951

In [39]:
np.linalg.norm(a, ord=1)


Out[39]:
2.0

In [40]:
np.linalg.norm(a, ord=np.inf)


Out[40]:
1.0

4.4 Matrix multiplication

$$ \begin{bmatrix} 1 & 2\\ 3 & 4 \end{bmatrix} \begin{bmatrix} -1 & 1\\ 2 & 1 \end{bmatrix} = %% do it column wise \begin{bmatrix} \left( -1 \cdot \begin{bmatrix} 1 \\ 3 \end{bmatrix} + 2 \cdot \begin{bmatrix} 2\\ 4 \end{bmatrix} \right) & \left( 1 \cdot \begin{bmatrix} 1 \\ 3 \end{bmatrix} + 1 \cdot \begin{bmatrix} 2\\ 4 \end{bmatrix} \right) \end{bmatrix} = \begin{bmatrix} 3 & 3\\ 5 & 7 \end{bmatrix} $$

Since Python 3.5, we can use the infix operator * for element-wise multiplication, and the infix operator @ for matrix multiplication.


In [51]:
A = np.array([[1,2],[3,4]])
B = np.array([[-1,1],[2,1]])
print(np.matmul(A, B)) # preferred over np.dot()
print(A @ B) # A and B must be numpy arrays
print(A * B)
print(B @ A)


[[3 3]
 [5 7]]
[[3 3]
 [5 7]]
[[-1  2]
 [ 6  4]]
[[2 2]
 [5 8]]

In [52]:
x = np.array([1, 2]).reshape((2,1))
print(A @ x)


[[ 5]
 [11]]

To solve $\mathbf{A z = B}$, we have $\mathbf{z = A^{-1} B}$


In [54]:
z = np.linalg.inv(A) @ B
z


Out[54]:
array([[ 4. , -1. ],
       [-2.5,  1. ]])

In [56]:
A @ z


Out[56]:
array([[-1.,  1.],
       [ 2.,  1.]])

In [ ]: